<!--
 * @Author: WuFeng <763467339@qq.com>
 * @Date: 2024-11-08 22:06:12
 * @LastEditTime: 2024-11-13 15:41:38
 * @LastEditors: WuFeng <763467339@qq.com>
 * @Description: 
 * @FilePath: \markdown-magic\src\pages\Pdf\page\entry\merge.vue
 * Copyright 版权声明
-->
<template>
  <div class="pageWrap">
    <a-spin :tip="spinningConfig.tip" :spinning="spinningConfig.spinning">
      <div class="descArea">
        在线将多个PDF文件合并成单个文件，可拖拽调整PDF文件先后顺序，可视化操作。纯浏览器实现不上传服务器100%保障您的数据安全！
      </div>
      <a-upload-dragger
        v-model:fileList="fileList"
        accept=".pdf,.PDF"
        name="file"
        :maxCount="10"
        :multiple="true"
        :showUploadList="false"
        action=""
        :before-upload="beforeUpload"
        :customRequest="customRequest"
        list-type="picture"
        @change="handleChange"
        @drop="handleDrop"
        @remove="handleRemove"
      >
        <p class="ant-upload-drag-icon">
          <inbox-outlined></inbox-outlined>
        </p>
        <p class="ant-upload-text">点击选择或拖拽文件到这里上传</p>
        <p class="ant-upload-hint">
          可上传10个PDF文件，单个不超过100MB（拖拽调整顺序）
        </p>
      </a-upload-dragger>
      <div class="dragWrap" v-show="fileList.length > 0">
        <div class="dragHeader">
          <div class="label name">
            名称
          </div>
          <div class="label page">
            页数
          </div>
          <div class="label range">
            输出范围
          </div>
          <div class="label status">
            状态
          </div>
          <div class="label handle">
            操作
          </div>
        </div>
        <draggable
          class="dragBody"
          :list="fileList"
          item-key="uid"
          ghost-class="ghost"
          chosen-class="chosen"
          @start="handleDragStart"
          @end="handleDragEnd"
          animation="300"
        >
          <template #item="{ element, index }">
            <div
              class="item"
              :class="{ 'not-draggable': !draggableState.enabled }"
              :key="element.id"
            >
              <div class="label name">
                <strong>
                  {{ element.name }}
                </strong>
              </div>
              <div class="label page">
                {{ element.pageCount }}
              </div>
              <div class="label range">
                <a-input-number 
                  v-model:value="element.processConfig.pageRange.start" 
                  @change="(val) => handleChangeRange(val, index, 'start')"
                  size="small" 
                  :min="1" 
                  :max="element?.pageCount" />
                <span class="line">
                  -
                </span>
                <a-input-number 
                  v-model:value="element.processConfig.pageRange.end" 
                  @change="(val) => handleChangeRange(val, index, 'end')"
                  size="small" 
                  :min="1" 
                  :max="element?.pageCount" />
              </div>
              <div class="label status">
                <a-tag color="orange" v-if="element.processConfig.status === '未合并'">未合并</a-tag>
                <a-tag color="orange" v-if="element.processConfig.status === '合并中'">合并中</a-tag>
                <a-tag color="red" v-if="element.processConfig.status === '合并异常'">合并异常</a-tag>
                <a-tag color="green" v-if="element.processConfig.status === '合并完成'">合并完成</a-tag>
              </div>
              <div class="label handle">
                <DeleteOutlined 
                  style="color: orangered;"
                  @click="handleDelete(element, index)" />
              </div>
            </div>
          </template>
        </draggable>
      </div>
      <div class="btnsWrap">
        <a-space>
          <a-button type="primary" @click="handleProcess"><PlayCircleOutlined />开始合并</a-button>
          <a-button v-if="mergedPdf" type="primary" @click="handlePreview"><FilePdfOutlined />预览文件</a-button>
          <a-button v-if="fileList.length > 0" danger @click="handleClear"><ClearOutlined />清空文件</a-button>
          <a-button v-if="mergedPdf" type="primary" ghost @click="handleDownload"><DownloadOutlined />下载</a-button>
        </a-space>
      </div>
      <!-- <pre id="output"></pre> -->
      <!-- <PdfPreview v-if="mergedPdf" :src="`http://localhost:1420/pdf1.pdf`" /> -->
      <PdfPreview v-if="mergePdfData" :src="mergePdfData" />
    </a-spin>
  </div>
</template>

<script setup>
import { ref, reactive, defineEmits, computed } from 'vue'
import { message } from 'ant-design-vue'
import {
  PlayCircleOutlined,
  FilePdfOutlined,
  DownloadOutlined,
  ClearOutlined,
  DeleteOutlined
} from '@ant-design/icons-vue'
import { PDFDocument } from 'pdf-lib'

import PdfPreview from '../../components/PdfPreview.vue'
import { downloadPdf } from '@/utils/index.js'

import draggable from 'vuedraggable'

import dayjs from 'dayjs'

const emit = defineEmits([])

// loading 配置
const spinningConfig = reactive({
  spinning: false,
  tip: '处理中...'
})

const fileList = ref([])

const mergedPdf = ref(null)

const mergePdfData = ref(null)

const handleChangeRange = (val, index, type) => {
  if (val === undefined || val === null || val === '') {
    if (type === 'start') {
      fileList.value[index].processConfig.pageRange.start = 1
    } else {
      fileList.value[index].processConfig.pageRange.end = fileList.value[index].pageCount
    }
  }
}

const draggableState = reactive({
  enabled: true,
  dragging: false,
})

const handleDragStart = () => {
  draggableState.dragging = true
}

const handleDragEnd = () => {
  draggableState.dragging = false
}

const handleDelete = (element, index) => {
  const { uid } = element
  fileList.value = fileList.value.filter(item => item.uid!== uid)
  message.success(`删除文件成功`)
}

const handlePreview = async () => {
  mergePdfData.value = null
  mergePdfData.value = await mergedPdf.value.save()
}

const handleRemove = (file) => {
  return true
}

const handleChange = (info) => {
  const status = info.file.status
  if (status === 'uploading') {
  } else if (status === 'done') {
    message.success(`${info.file.name} 文件上传成功.`)
  } else if (status === 'error') {
    message.error(`${info.file.name} 文件上传错误.`)
  } else if (status === 'removed') {
    message.error(`${info.file.name} 文件删除成功.`)
  }
}
const handleDrop = (e) => {
  console.log(e)
}

const beforeUpload = (file) => {
  const isJpgOrPng = file.type === 'application/pdf';
  if (!isJpgOrPng) {
    message.error('请上传pdf格式的文件!');
  }
  const isLt2M = file.size / 1024 / 1024 < 1000;
  if (!isLt2M) {
    message.error('文件必须小于1000MB!');
  }
  file.processConfig = {
    pageRange: {
      start: 1,
      end: 1
    },
    status: '未合并'
  }
  return isJpgOrPng && isLt2M;
}

// 自定义上传
const customRequest = async ({ action, file, onSuccess, onError }) => {
  spinningConfig.spinning = true
  spinningConfig.tip = '上传中...'
  try {
    fileList.value.find((item, index) => {
      if (item.uid === file.uid) {
        item.status = 'done'
        
        // 读取文件
        const reader = new FileReader();
        reader.onload = async function (e) {
          // 你可以在这里处理二进制内容
          const arrayBuffer = this.result;
    
          item.arrayBuffer = arrayBuffer
    
          let existingPdf = null
          try {
            // 加载现有的 PDF 文档
            existingPdf = await PDFDocument.load(arrayBuffer);
          } catch (error) {
            message.error(`文件【${item.name}】有密码保护，无法解析，无法合并！`)
            // 关闭loading
            spinningConfig.spinning = false
          }
          if (existingPdf === null) {
            fileList.value.splice(index, 1)
            return
          }
    
          // 获取所有页面
          item.existingPdf = existingPdf
          item.pages = existingPdf.getPages();
          item.pageCount = existingPdf.getPageCount();
  
          // 初始化配置项
          item.processConfig = {
            pageRange: {
              start: 1,
              end: item.pageCount
            },
            status: '未合并'
          }
  
          // 关闭loading
          spinningConfig.spinning = false
  
        }
    
        reader.readAsArrayBuffer(item.originFileObj);
      }
      return item.uid === file.uid
    })
  } catch (error) {
    // 关闭loading
    spinningConfig.spinning = false
    message.error(error)
  }
}

// 开始合并
const handleProcess = async () => {
  if (fileList.value.length < 2) {
    message.warning('请上传至少两个pdf文件!')
    return
  }

  spinningConfig.spinning = true
  spinningConfig.tip = '合并中...请耐心等待...'

  // 初始化合并后的文件
  mergePdfData.value = null
  // 创建一个新的 PDF 文档
  mergedPdf.value = await PDFDocument.create();

  // 文件总个数
  const fileListLength = fileList.value.length

  const startTime = new Date()
  let totalPageCount = 0

  // 遍历文件列表
  for (const i in fileList.value) {
    const item = fileList.value[i]
    console.log(`开始处理-----------文件【${item.name}】------${dayjs().format('HH:mm:ss')}`)
    // 当前文件处理中，更新状态
    item.processConfig.status = '合并中'
    // 读取文件
    const reader = new FileReader();
    reader.onload = async function (e) {
      
      // 获取所有页面
      const pages = item.pages
      
      // 遍历每一页
      for (const ii in pages) {

        // 当前是第一页
        const currentPage = ii * 1 + 1
        
        // console.log(`🚀 ~ currentPage:`, currentPage)
        // console.log(`🚀 ~ item.processConfig.pageRange.start:`, item.processConfig.pageRange.start)
        // console.log(`🚀 ~ currentPage > item.processConfig.pageRange.end:`, item.processConfig.pageRange.end)

        if (currentPage >= item.processConfig.pageRange.start && currentPage <= item.processConfig.pageRange.end) {
          const page = pages[ii]
          // 将页面复制到新的 PDF 文档中
          const copiedPage = await mergedPdf.value.copyPages(item.existingPdf, [ii])
          mergedPdf.value.addPage(copiedPage[0])
          
          totalPageCount += 1
  
          console.log(`--------正在处理文件【${item.name}】的第${currentPage}页...------${dayjs().format('HH:mm:ss')}`)
        }

        // 当前文档处理完毕后，更新状态
        if ((currentPage) === item.pageCount) {
          item.processConfig.status = '合并完成'
          console.log(`处理完成-----------文件【${item.name}】--------------共${item.pageCount}页---------------------${dayjs().format('HH:mm:ss')}`)
        }
  
        // 如果是最后一个文件 的 最后一页处理完后，关闭loading
        if ((i * 1 + 1) === fileListLength && (currentPage) === item.pageCount) {
          const endTime = new Date()
          const totalTime = (endTime - startTime) / 1000
          console.log(`---------------------------合并完成，共处理${fileListLength}个文件，共${totalPageCount}页，总耗时：${totalTime}秒`)
          spinningConfig.spinning = false
          message.success(`合并成功`)
          return
        }

      }

      // // 加载 PDF 文档
      // const pdfDoc = await PDFDocument.load(arrayBuffer);

      // // 获取 PDF 元数据
      // const info = pdfDoc.getAuthor()
      // 	? {
      // 			title: await pdfDoc.getTitle(),
      // 			author: await pdfDoc.getAuthor(),
      // 			subject: await pdfDoc.getSubject(),
      // 			creator: await pdfDoc.getCreator(),
      // 			producer: await pdfDoc.getProducer(),
      // 			creationDate: pdfDoc.getCreationDate(),
      // 			modificationDate: pdfDoc.getModificationDate(),
      //   }
      // 	: 'No metadata available';

      // // 显示 PDF 元数据
      // document.getElementById('output').textContent += JSON.stringify(info, null, 2);
    }

    reader.readAsArrayBuffer(item.originFileObj);

  }

}

// 清空文件
const handleClear = () => {
  fileList.value = []
  mergedPdf.value = null
  mergePdfData.value = null
  message.success(`清空文件成功`)
}

// 下载文件
const handleDownload = async () => {
  downloadPdf(await mergedPdf.value.save(), 'merged.pdf')
}
</script>

<style lang="scss" scoped>
.pageWrap {
  :deep(.ant-upload-wrapper .ant-upload-drag){
    height: auto !important;
  }
  .descArea{
    line-height: 1.6em;
    font-size: 14px;
    color: #666;
    margin-bottom: 12px;
  }
  .dragWrap{
    padding-top: 12px;
    .label{
      padding: 12px;
    }
    .name{
      flex: 1;
    }
    .page{
      width: 80px;
      text-align: center;
    }
    .range{
      width: 160px;
      display: flex;
      align-items: center;
      justify-content: center;
      .line{
        margin: 0 5px;
      }
    }
    .status{
      width: 80px;
      text-align: center;
    }
    .handle{
      width: 120px;
      text-align: right;
    }
    .dragHeader{
      display: flex;
      align-items: center;
      margin-bottom: 3px;
      border: 1px solid #eee;
      background-color: #f0f0f0;
      .label{
        font-size: 14px;
        color: #333;
        font-weight: bold;
      }
    }
    .dragBody{
      .item {
        margin-bottom: 3px;
        cursor: move;
        background-color: #f9f9f9;
        border: 1px solid #eee;
        display: flex;
        &:hover {
          background-color: #f0f0f0;
        }
        .name{
          strong{
            font-weight: normal;
            font-size: 14px;
          }
        }

        .handle{
          cursor: pointer;
        }
      }
      .not-draggable {
        cursor: no-drop;
      }
      .ghost {
        opacity: 0.5;
        border: 1px solid #18a058;
      }
      .chosen {
        border: 1px solid #18a058;
      }
    }
  }
  .btnsWrap{
    margin: 16px 0;
  }
}
</style>